﻿//-----------------------------------------------------------------------
// <author>Paul Long</author>
//-----------------------------------------------------------------------

namespace NetworkAddresses
{
    using System;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using Microsoft.NetworkMonitor;
    using TopUserExceptions;

    /// <summary>
    /// Maintains information for an IPv6 address.
    /// </summary>
    internal class IPv6Address : Address
    {
        /// <summary>
        /// Raw address from frame.
        /// </summary>
        private byte[] address;

        /// <summary>
        /// Initializes a new instance of the IPv6Address class.
        /// Retrieves the IPv6 information from the frame given the parsed frame and Data field ID.
        /// </summary>
        /// <param name="parsedFrame">Parsed frame object</param>
        /// <param name="id">ID returned from NmAddField</param>
        public unsafe IPv6Address(IntPtr parsedFrame, uint id)
        {
            NmParsedFieldInfo parsedDataFieldInfo = new NmParsedFieldInfo();
            parsedDataFieldInfo.Size = (ushort)Marshal.SizeOf(typeof(NmParsedFieldInfo));
            uint errno = NetmonAPI.NmGetParsedFieldInfo(parsedFrame, id, (uint)0, ref parsedDataFieldInfo);
            if (errno != 0)
            {
                throw new ParsingException("NmGetParsedField(...) failed, errno = " + errno);
            }

            byte[] buffer = null;

            uint length = parsedDataFieldInfo.ValueBufferLength;
            if (length > 0)
            {
                byte* rightOperand = (byte*)Marshal.AllocCoTaskMem((int)length);
                uint actuallength = 0;

                errno = NetmonAPI.NmGetFieldInBuffer(parsedFrame, id, length, rightOperand, out actuallength);

                if (errno != 0)
                {
                    throw new ParsingException("NmGetFieldInBuffer(...) failed, errno = " + errno);
                }

                if (actuallength < (uint)length)
                {
                    Console.WriteLine(
                                "Warning: NmGetFieldInBuffer(...) actual copied length:{0} different with buffer length:{1}",
                                actuallength, 
                                length);
                }

                buffer = new byte[actuallength];
                Marshal.Copy((IntPtr)rightOperand, buffer, 0, buffer.Length);

                Marshal.FreeCoTaskMem((IntPtr)rightOperand);
            }

            if (buffer != null)
            {
                this.address = buffer;
                Display = String.Format(
                            CultureInfo.CurrentCulture, 
                            "{0:X}:{1:X}:{2:X}:{3:X}:{4:X}:{5:X}:{6:X}:{7:X}",
                            (ushort)this.address[0] << 8 | (ushort)this.address[1],
                            (ushort)this.address[2] << 8 | (ushort)this.address[3],
                            (ushort)this.address[4] << 8 | (ushort)this.address[5],
                            (ushort)this.address[6] << 8 | (ushort)this.address[7],
                            (ushort)this.address[8] << 8 | (ushort)this.address[9],
                            (ushort)this.address[10] << 8 | (ushort)this.address[11],
                            (ushort)this.address[12] << 8 | (ushort)this.address[13],
                            (ushort)this.address[14] << 8 | (ushort)this.address[15]);
            }
            else
            {
                this.address = null;
            }
        }

        /// <summary>
        /// Compares two IPV6 address for equality.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if equal.</returns>
        public static bool operator ==(IPv6Address leftOperand, IPv6Address rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if ((object)leftOperand.address == null || (object)rightOperand.address == null)
            {
                return false;
            }

            for (int n = 0; n < 10; n++)
            {
                if (leftOperand.address[n] != rightOperand.address[n])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Compares two IPV6 address for not equal.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if equal.</returns>
        public static bool operator !=(IPv6Address leftOperand, IPv6Address rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if ((object)leftOperand.address == null || (object)rightOperand.address == null)
            {
                return false;
            }

            for (int n = 0; n < 10; n++)
            {
                if (leftOperand.address[n] != rightOperand.address[n])
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Compares two IPv6 address for less than.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if greater than.</returns>
        public static bool operator >(IPv6Address leftOperand, IPv6Address rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if ((object)leftOperand.address == null || (object)rightOperand.address == null)
            {
                return false;
            }

            for (int n = 0; n < 10; n++)
            {
                if (leftOperand.address[n] > rightOperand.address[n])
                {
                    return true;
                }
                else if (leftOperand.address[n] < rightOperand.address[n])
                {
                    return false;
                }
            }

            return false;
        }

        /// <summary>
        /// Compares two IPv6 address for less than.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if less than.</returns>
        public static bool operator <(IPv6Address leftOperand, IPv6Address rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if ((object)leftOperand.address == null || (object)rightOperand.address == null)
            {
                return false;
            }

            for (int n = 0; n < 10; n++)
            {
                if (leftOperand.address[n] < rightOperand.address[n])
                {
                    return true;
                }
                else if (leftOperand.address[n] < rightOperand.address[n])
                {
                    return false;
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if the address is valid.
        /// </summary>
        /// <returns>True if address is valid.</returns>
        public bool IsValid()
        {
            if (this.address != null)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        
        /// <summary>
        /// Equality for IPv6
        /// </summary>
        /// <param name="obj">Object to compare to.</param>
        /// <returns>True if objects are equal</returns>
        public override bool Equals(object obj)
        {
            return (IPv6Address)obj == this;
        }

        /// <summary>
        /// Returns hash code
        /// </summary>
        /// <returns>int hash code</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}
